﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace DlrBasic.Runtime
{
    /// <summary>
    /// BASIC standard functions.
    /// TODO extend
    /// </summary>
    public static class DlrBasicFunctions
    {
        [DlrBasicFunction("MID$")]
        public static string Mid(string s, short n)
        {
            if (n > s.Length)
                return String.Empty;
            else
                return s.Substring(n - 1);
        }

        [DlrBasicFunction("RIGHT")]
        public static string RightAfter(string s, short n)
        {
            if (n > s.Length)
                return String.Empty;
            else
                return s.Substring(n - 1);
        }

        [DlrBasicFunction("MID"), DlrBasicFunction("MID$")]
        public static string Mid(string s, short n, short m)
        {
            if (n > s.Length)
                return String.Empty;
            else
                return s.Substring(n - 1, Math.Min(s.Length - n + 1, m));
        }

        [DlrBasicFunction("LEFT"), DlrBasicFunction("LEFT$")]
        public static string Left(string s, short n)
        {
            if (n > s.Length)
                return s;
            else
                return s.Substring(0, n);
        }

        [DlrBasicFunction("RIGHT$")]
        public static string Right(string s, short n)
        {
            if (n > s.Length)
                return s;
            else
                return s.Substring(s.Length - n);
        }

        [DlrBasicFunction("LEN")]
        public static short Len(string s)
        {
            return (short)s.Length;
        }

        [DlrBasicFunction("RND")]
        public static float Rnd()
        {
            return Rnd(1);
        }

        static float lastRndValue = 0;

        [DlrBasicFunction("RND")]
        public static float Rnd(short n)
        {
            if (n > 0)
                return lastRndValue = DlrBasicHelpers.GetNextRandom();
            else
                return lastRndValue;
        }

        [DlrBasicFunction("SQR")]
        public static double Sqr(double op)
        {
            return Math.Sqrt(op);
        }

        [DlrBasicFunction("SQR")]
        public static float Sqr(float op)
        {
            return (float)Math.Sqrt(op);
        }

        [DlrBasicFunction("CHR$"), DlrBasicFunction("CHR")]
        public static string Chr(short code)
        {
            return ((char)code).ToString();
        }

        [DlrBasicFunction("ASC")]
        public static short Asc(string s)
        {
            return (short)s[0];
        }

        [DlrBasicFunction("INT")]
        public static short Int(short op)
        {
            return op;
        }

        [DlrBasicFunction("INT")]
        public static float Int(float op)
        {
            return (float)Math.Floor(op);
        }

        [DlrBasicFunction("INT")]
        public static double Int(double op)
        {
            return Math.Floor(op);
        }

        [DlrBasicFunction("FIX")]
        public static short Fix(short op)
        {
            return op;
        }

        [DlrBasicFunction("FIX")]
        public static float Fix(float op)
        {
            return (float)Math.Truncate(op);
        }

        [DlrBasicFunction("FIX")]
        public static double Fix(double op)
        {
            return Math.Truncate(op);
        }

        [DlrBasicFunction("CINT")]
        public static short CInt(short op)
        {
            return op;
        }

        [DlrBasicFunction("CINT")]
        public static short CInt(float op)
        {
            return checked((short)Math.Round(op));
        }

        [DlrBasicFunction("CINT")]
        public static short CInt(double op)
        {
            return checked((short)Math.Round(op));
        }

        [DlrBasicFunction("CSNG")]
        public static float CSng(short op)
        {
            return (float)op;
        }

        [DlrBasicFunction("CSNG")]
        public static float CSng(float op)
        {
            return op;
        }

        [DlrBasicFunction("CSNG")]
        public static float CSng(double op)
        {
            return (float)op;
        }

        [DlrBasicFunction("CDBL")]
        public static double CDbl(short op)
        {
            return (double)op;
        }

        [DlrBasicFunction("CDBL")]
        public static double CDbl(float op)
        {
            return (double)op;
        }

        [DlrBasicFunction("CDBL")]
        public static double CDbl(double op)
        {
            return op;
        }

        [DlrBasicFunction("ABS")]
        public static short Abs(short value)
        {
            return checked((short)(value < 0 ? -value : value));
        }

        [DlrBasicFunction("ABS")]
        public static float Abs(float value)
        {
            return value < 0 ? -value : value;
        }

        [DlrBasicFunction("ABS")]
        public static double Abs(double value)
        {
            return value < 0 ? -value : value;
        }

        [DlrBasicFunction("TIMER")]
        public static float Timer()
        {
            return (float)DateTime.Now.TimeOfDay.TotalSeconds;
        }

        [DlrBasicFunction("TAB")]
        public static string Tab(short column)
        {
            int width = column - DlrBasicHelpers.CurrentColumn - 1;
            if (width > 0)
                return new String(' ', width);
            else
                return String.Empty;
        }

        [DlrBasicFunction("SPC"), DlrBasicFunction("SPACE$")]
        public static string Space(short x)
        {
            if (x > 0)
                return new String(' ', x);
            else
                return String.Empty;
        }

        [DlrBasicFunction("POS")]
        public static short Pos(short c)
        {
            return (short)(DlrBasicHelpers.CurrentColumn + 1);
        }


        [DlrBasicFunction("VAL")]
        public static float Val(string s)
        {
            float value;
            Single.TryParse(s, out value);
            return value;
        }

        [DlrBasicFunction("INKEY$")]
        public static string InputKey()
        {
            return DlrBasicHelpers.InputKey();
        }

        [DlrBasicFunction("TIME")]
        public static float Time(short param)
        {
            return Timer();
        }

        [DlrBasicFunction("STR$")]
        public static string Str(short value)
        {
            return DlrBasicHelpers.FormatNumber(value);
        }

        [DlrBasicFunction("STR$")]
        public static string Str(float value)
        {
            return DlrBasicHelpers.FormatNumber(value);
        }

        [DlrBasicFunction("STR$")]
        public static string Str(double value)
        {
            return DlrBasicHelpers.FormatNumber(value);
        }

        [DlrBasicFunction("TIME$")]
        public static string Time()
        {
            return DateTime.Now.ToString("HH:mm:ss");
        }

        [DlrBasicFunction("DATE$")]
        public static string Date()
        {
            return DateTime.Now.ToString("MM-dd-yyyy");
        }

        static string[] FunctionNames;

        static DlrBasicFunctions()
        {
            Dictionary<string, DlrBasicFunctionAttribute> names = new Dictionary<string,DlrBasicFunctionAttribute>();
            foreach (MethodInfo method in typeof(DlrBasicFunctions).GetMethods(BindingFlags.Static | BindingFlags.Public))
            {
                foreach (DlrBasicFunctionAttribute attr in method.GetCustomAttributes(typeof(DlrBasicFunctionAttribute), false))
                {
                    names[attr.Name.ToUpperInvariant()] = attr;
                }
            }
            List<string> sortedNames = new List<string>(names.Keys);
            sortedNames.Sort();
            FunctionNames = sortedNames.ToArray();
        }

        internal static bool IsFunctionPresent(string name)
        {
            return Array.BinarySearch(FunctionNames, name.ToUpperInvariant()) >= 0;
        }

        public static MethodInfo GetBindableMethod(string name, Type[] argumentTypes)
        {
            MethodInfo minScoreMethod = null;
            int minScore = int.MaxValue;
            foreach (MethodInfo method in typeof(DlrBasicFunctions).GetMethods(BindingFlags.Static | BindingFlags.Public))
            {
                ParameterInfo[] parameters = method.GetParameters();
                if (parameters.Length != argumentTypes.Length)
                    continue;

                foreach (DlrBasicFunctionAttribute attr in method.GetCustomAttributes(typeof(DlrBasicFunctionAttribute), false))
                {
                    if (attr.Name == name)
                    {
                        bool isMatch = true;
                        int currentScore = 0;
                        for (int i = 0; i < parameters.Length; i++)
			            {
                            int? conversionScore = DlrBasicConvert.ConversionScore(parameters[i].ParameterType, argumentTypes[i]);

                            if (!conversionScore.HasValue) 
                            {
                                isMatch = false; break;
                            }

                            currentScore += conversionScore.Value;
			            }

                        if (isMatch && currentScore < minScore)
                        {
                            minScore = currentScore;
                            minScoreMethod = method;
                        }

                        break;
                    }
                }
            }

            return minScoreMethod;
        }
    }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple=true)]
    public class DlrBasicFunctionAttribute : Attribute
    {
        string name;

        public string Name
        {
            get { return name; }
        }

        public DlrBasicFunctionAttribute(string name)
        {
            this.name = name;
        }
    }
}
